use std::error::Error;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io;
use std::io::Read;
use std::num;
use std::num::ParseIntError;

#[cfg(test)]
mod tests {
    #[test]
    fn test_error() {
        super::start();
    }
}

/// 自定义错误
pub fn start() {
    let result = run_two("../tao_book/src/ninth/test_num.txt");
    println!("{:?}", result);
}

fn run(filename: &str) -> Result<i32, CliError> {
    File::open(filename)
        .map_err(CliError::Io)
        .and_then(|mut f| {
            let mut contents = String::new();
            f.read_to_string(&mut contents)
                .map_err(CliError::Io)
                .map(|_| contents)
        })
        .and_then(|contents| {
            let mut sum = 0;
            for c in contents.lines() {
                match c.parse::<i32>() {
                    Ok(n) => sum += n,
                    Err(e) => return Err(CliError::ParseInt(e)),
                }
            }
            Ok(sum)
        })
}

fn run_two(filename: &str) -> Result<i32, CliError> {
    let mut file = File::open(filename)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    let mut sum = 0;
    for c in contents.lines() {
        sum += c.parse::<i32>()?;
    }
    Ok(sum)
}

#[derive(Debug)]
enum CliError {
    Io(io::Error),
    ParseInt(num::ParseIntError),
    ParseFloat(num::ParseFloatError),
}

impl Display for CliError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            CliError::Io(e) => {
                write!(f, "IO error : {}", e)
            }
            CliError::ParseInt(e) => {
                write!(f, "Parse error : {}", e)
            }
            CliError::ParseFloat(e) => {
                write!(f, "Parse error : {}", e)
            }
        }
    }
}

impl Error for CliError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            CliError::Io(e) => e.source(),
            CliError::ParseInt(e) => Some(e),
            CliError::ParseFloat(e) => Some(e),
        }
    }
}

impl From<io::Error> for CliError {
    fn from(e: io::Error) -> Self {
        CliError::Io(e)
    }
}

impl From<num::ParseIntError> for CliError {
    fn from(e: num::ParseIntError) -> Self {
        CliError::ParseInt(e)
    }
}

impl From<num::ParseFloatError> for CliError {
    fn from(e: num::ParseFloatError) -> Self {
        CliError::ParseFloat(e)
    }
}
